/*
 * UAE - The Un*x Amiga Emulator
 *
 * CIA chip support
 *
 * Copyright 1995 Bernd Schmidt, Alessandro Bissacco
 * Copyright 1996, 1997 Stefan Reinauer, Christian Schmitt
 */

#include "sysconfig.h"
#include "cia.h"
#include "serial.h"
#include "parallel.h"
#include "ersatz.h"
#include "sampler.h"
#include "dongle.h"
#include "options.h"
#include "akiko.h"
#include "ar.h"
#include "audio.h"
#include "sound.h"
#include "cdtv.h"
#include "keyboard.h"
#include "inputdevice.h"
#include "savestate.h"
#include "uae.h"
#include "gui.h"
#include "zfile.h"
#include "disk.h"
#include "debug.h"
#include "keybuf.h"
#include "posixemu.h"
#include "custom.h"
#include "newcpu.h"
#include "inputrecord.h"

#define TOD_HACK

/* e-clock is 10 CPU cycles, 4 cycles high, 6 low
 * data transfer happens during 4 high cycles
 */
#define ECLOCK_DATA_CYCLE 4
#define ECLOCK_WAIT_CYCLE 6

#define DIV10 ((ECLOCK_DATA_CYCLE + ECLOCK_WAIT_CYCLE) * CYCLE_UNIT / 2) /* Yes, a bad identifier. */
#define CIASTARTCYCLESHI 3
#define CIASTARTCYCLESCRA 2

static uint ciaaicr, ciaaimask, ciabicr, ciabimask;
static uint ciaacra, ciaacrb, ciabcra, ciabcrb;
static uint ciaastarta, ciaastartb, ciabstarta, ciabstartb;
static uint ciaaicr_reg, ciabicr_reg;

/* Values of the CIA timers.  */
static uint ciaata, ciaatb, ciabta, ciabtb;
/* Computed by compute_passed_time.  */
static uint ciaata_passed, ciaatb_passed, ciabta_passed, ciabtb_passed;

static uint ciaatod, ciabtod, ciaatol, ciabtol, ciaaalarm, ciabalarm;
static int ciaatlatch, ciabtlatch;
static bool oldled, oldovl, oldcd32mute;
static bool led;
static int led_old_brightness;
static uint led_cycles_on, led_cycles_off, led_cycle;

uint ciabpra;

static uint ciaala, ciaalb, ciabla, ciablb;
static int ciaatodon, ciabtodon;
static uint ciaapra, ciaaprb, ciaadra, ciaadrb, ciaasdr, ciaasdr_cnt;
static uint ciabprb, ciabdra, ciabdrb, ciabsdr, ciabsdr_cnt;
static int div10;
static int kbstate, kback, ciaasdr_unread;
static uint sleepyhead;

static byte serbits;
static int warned = 10;

static void setclr(uint* p, uint val)
{
    if (val & 0x80)
    {
        *p |= val & 0x7F;
    }
    else
    {
        *p &= ~val;
    }
}

static void ICRA(uint data)
{
    if (ciaaimask & ciaaicr)
    {
        ciaaicr |= 0x80;
        INTREQ_0(0x8000 | data);
    }
    ciaaicr_reg |= ciaaicr;
}

static void ICRB(uint data)
{
    if (ciabimask & ciabicr)
    {
        ciabicr |= 0x80;
        INTREQ_0(0x8000 | data);
    }
    ciabicr_reg |= ciabicr;
}

static void RethinkICRA()
{
    if (ciaaicr)
    {
        if (g_curr_conf.cpu_cycle_exact)
            event2_newevent_xx(-1, 2 * CYCLE_UNIT + CYCLE_UNIT / 2, 0x0008, ICRA);
        else
            ICRA(0x0008);
    }
}

static void RethinkICRB()
{
    if (ciabicr)
    {
        if (g_curr_conf.cpu_cycle_exact)
            event2_newevent_xx(-1, 2 * CYCLE_UNIT + CYCLE_UNIT / 2, 0x2000, ICRB);
        else
            ICRB(0x2000);
    }
}

void rethink_cias()
{
    RethinkICRA();
    RethinkICRB();
}

/* Figure out how many CIA timer cycles have passed for each timer since the
   last call of CIA_calctimers.  */

static void compute_passed_time()
{
    uint ccount = (get_cycles() - eventtab[ev_cia].oldcycles + div10);
    uint ciaclocks = ccount / DIV10;

    ciaata_passed = ciaatb_passed = ciabta_passed = ciabtb_passed = 0;

    /* CIA A timers */
    if ((ciaacra & 0x21) == 0x01)
    {
        uint cc = ciaclocks;
        if (cc > ciaastarta)
            cc -= ciaastarta;
        else
            cc = 0;
        ASSERT((ciaata + 1) >= cc);
        ciaata_passed = cc;
    }
    if ((ciaacrb & 0x61) == 0x01)
    {
        uint cc = ciaclocks;
        if (cc > ciaastartb)
            cc -= ciaastartb;
        else
            cc = 0;
        ASSERT((ciaatb + 1) >= cc);
        ciaatb_passed = cc;
    }

    /* CIA B timers */
    if ((ciabcra & 0x21) == 0x01)
    {
        uint cc = ciaclocks;
        if (cc > ciabstarta)
            cc -= ciabstarta;
        else
            cc = 0;
        ASSERT((ciabta + 1) >= cc);
        ciabta_passed = cc;
    }
    if ((ciabcrb & 0x61) == 0x01)
    {
        uint cc = ciaclocks;
        if (cc > ciabstartb)
            cc -= ciabstartb;
        else
            cc = 0;
        ASSERT((ciabtb + 1) >= cc);
        ciabtb_passed = cc;
    }
}

/* Called to advance all CIA timers to the current time.  This expects that
   one of the timer values will be modified, and CIA_calctimers will be called
   in the same cycle.  */

static int CIA_update_check()
{
    uint ccount = (get_cycles() - eventtab[ev_cia].oldcycles + div10);
    uint ciaclocks = ccount / DIV10;

    int aovfla = 0, aovflb = 0, asp = 0, bovfla = 0, bovflb = 0, bsp = 0;
    int icr = 0;

    div10 = ccount % DIV10;

    /* CIA A timers */
    if ((ciaacra & 0x21) == 0x01)
    {
        bool check = true;
        uint cc = ciaclocks;
        if (ciaastarta > 0)
        {
            if (cc > ciaastarta)
            {
                cc -= ciaastarta;
                ciaastarta = 0;
            }
            else
            {
                ciaastarta -= cc;
                check = false;
            }
        }
        if (check)
        {
            ASSERT((ciaata + 1) >= cc);
            if ((ciaata + 1) == cc)
            {
                if ((ciaacra & 0x48) == 0x40 && ciaasdr_cnt > 0 && --ciaasdr_cnt == 0)
                    asp = 1;
                aovfla = 1;
                if ((ciaacrb & 0x61) == 0x41 || (ciaacrb & 0x61) == 0x61)
                {
                    if (ciaatb-- == 0)
                        aovflb = 1;
                }
            }
            ciaata -= cc;
        }
    }
    if ((ciaacrb & 0x61) == 0x01)
    {
        bool check = true;
        uint cc = ciaclocks;
        if (ciaastartb > 0)
        {
            if (cc > ciaastartb)
            {
                cc -= ciaastartb;
                ciaastartb = 0;
            }
            else
            {
                ciaastartb -= cc;
                check = false;
            }
        }
        if (check)
        {
            ASSERT((ciaatb + 1) >= cc);
            if ((ciaatb + 1) == cc)
                aovflb = 1;
            ciaatb -= cc;
        }
    }

    /* CIA B timers */
    if ((ciabcra & 0x21) == 0x01)
    {
        bool check = true;
        uint cc = ciaclocks;
        if (ciabstarta > 0)
        {
            if (cc > ciabstarta)
            {
                cc -= ciabstarta;
                ciabstarta = 0;
            }
            else
            {
                ciabstarta -= cc;
                check = false;
            }
        }
        if (check)
        {
            ASSERT((ciabta + 1) >= cc);
            if ((ciabta + 1) == cc)
            {
                if ((ciabcra & 0x48) == 0x40 && ciabsdr_cnt > 0 && --ciabsdr_cnt == 0)
                    bsp = 1;
                bovfla = 1;
                if ((ciabcrb & 0x61) == 0x41 || (ciabcrb & 0x61) == 0x61)
                {
                    if (ciabtb-- == 0)
                        bovflb = 1;
                }
            }
            ciabta -= cc;
        }
    }
    if ((ciabcrb & 0x61) == 0x01)
    {
        bool check = true;
        uint cc = ciaclocks;
        if (ciabstartb > 0)
        {
            if (cc > ciabstartb)
            {
                cc -= ciabstartb;
                ciabstartb = 0;
            }
            else
            {
                ciabstartb -= cc;
                check = false;
            }
        }
        if (check)
        {
            ASSERT((ciabtb + 1) >= cc);
            if ((ciabtb + 1) == cc)
                bovflb = 1;
            ciabtb -= cc;
        }
    }

    if (aovfla)
    {
        ciaaicr |= 1;
        icr = 1;
        ciaata = ciaala;
        if (ciaacra & 0x8)
        {
            ciaacra &= ~1;
        }
    }
    if (aovflb)
    {
        ciaaicr |= 2;
        icr = 1;
        ciaatb = ciaalb;
        if (ciaacrb & 0x8)
        {
            ciaacrb &= ~1;
        }
    }
    if (asp)
    {
        ciaaicr |= 8;
        icr = 1;
    }
    if (bovfla)
    {
        // write_log (L"INT %x\n", M68K_GETPC);
        ciabicr |= 1;
        icr |= 2;
        ciabta = ciabla;
        if (ciabcra & 0x8)
        {
            ciabcra &= ~1;
        }
    }
    if (bovflb)
    {
        ciabicr |= 2;
        icr |= 2;
        ciabtb = ciablb;
        if (ciabcrb & 0x8)
        {
            ciabcrb &= ~1;
        }
    }
    if (bsp)
    {
        ciabicr |= 8;
        icr |= 2;
    }
    return icr;
}

static void CIA_update()
{
    int icr = CIA_update_check();
    if (icr & 1)
        RethinkICRA();
    if (icr & 2)
        RethinkICRB();
}

/* Call this only after CIA_update has been called in the same cycle.  */

static void CIA_calctimers()
{
    long int ciaatimea = -1, ciaatimeb = -1, ciabtimea = -1, ciabtimeb = -1;
    int div10diff = DIV10 - div10;

    eventtab[ev_cia].oldcycles = get_cycles();
    if ((ciaacra & 0x21) == 0x01)
    {
        ciaatimea = div10diff + DIV10 * (ciaata + ciaastarta);
    }
    #if 0
    if ((ciaacrb & 0x61) == 0x41)
    {
        /* Timer B will not get any pulses if Timer A is off. */
        if (ciaatimea >= 0)
        {
            /* If Timer A is in one-shot mode, and Timer B needs more than
             * one pulse, it will not underflow. */
            if (ciaatb == 0 || (ciaacra & 0x8) == 0)
            {
                /* Otherwise, we can determine the time of the underflow. */
                /* This may overflow, however.  So just ignore this timer and
                   use the fact that we'll call CIA_handler for the A timer.  */
                /* ciaatimeb = ciaatimea + ciaala * DIV10 * ciaatb; */
            }
        }
    }
    #endif
    if ((ciaacrb & 0x61) == 0x01)
    {
        ciaatimeb = div10diff + DIV10 * (ciaatb + ciaastartb);
    }

    if ((ciabcra & 0x21) == 0x01)
    {
        ciabtimea = div10diff + DIV10 * (ciabta + ciabstarta);
    }
    #if 0
    if ((ciabcrb & 0x61) == 0x41)
    {
        /* Timer B will not get any pulses if Timer A is off. */
        if (ciabtimea >= 0)
        {
            /* If Timer A is in one-shot mode, and Timer B needs more than
             * one pulse, it will not underflow. */
            if (ciabtb == 0 || (ciabcra & 0x8) == 0)
            {
                /* Otherwise, we can determine the time of the underflow. */
                /* ciabtimeb = ciabtimea + ciabla * DIV10 * ciabtb; */
            }
        }
    }
    #endif
    if ((ciabcrb & 0x61) == 0x01)
    {
        ciabtimeb = div10diff + DIV10 * (ciabtb + ciabstartb);
    }
    eventtab[ev_cia].active = (ciaatimea != -1 || ciaatimeb != -1
        || ciabtimea != -1 || ciabtimeb != -1);
    if (eventtab[ev_cia].active)
    {
        uint ciatime = ~0L;
        if (ciaatimea != -1)
            ciatime = ciaatimea;
        if (ciaatimeb != -1 && ciaatimeb < ciatime)
            ciatime = ciaatimeb;
        if (ciabtimea != -1 && ciabtimea < ciatime)
            ciatime = ciabtimea;
        if (ciabtimeb != -1 && ciabtimeb < ciatime)
            ciatime = ciabtimeb;
        eventtab[ev_cia].evtime = ciatime + get_cycles();
    }
    events_schedule();
}

void CIA_handler()
{
    CIA_update();
    CIA_calctimers();
}

void cia_diskindex()
{
    ciabicr |= 0x10;
    RethinkICRB();
}
void cia_parallelack()
{
    ciaaicr |= 0x10;
    RethinkICRA();
}

static int checkalarm(uint tod, uint alarm, int inc)
{
    if (tod == alarm)
        return 1;
    if (!inc)
        return 0;
    /* emulate buggy TODMED counter.
     * it counts: .. 29 2A 2B 2C 2D 2E 2F 20 30 31 32 ..
     * (2F->20->30 only takes couple of cycles but it will trigger alarm..
     */
    if (tod & 0x000fff)
        return 0;
    if (((tod - 1) & 0xfff000) == alarm)
        return 1;
    return 0;
}

static __forceinline void ciab_checkalarm(int inc)
{
    if (checkalarm(ciabtod, ciabalarm, inc))
    {
        ciabicr |= 4;
        RethinkICRB();
    }
}

static __forceinline void ciaa_checkalarm(int inc)
{
    if (checkalarm(ciaatod, ciaaalarm, inc))
    {
        ciaaicr |= 4;
        RethinkICRA();
    }
}

#ifdef TOD_HACK
static ulong64 tod_hack_tv, tod_hack_tod, tod_hack_tod_last;
static int tod_hack_enabled;
static void tod_hack_reset()
{
    struct timeval tv;
    gettimeofday(&tv, nullptr);
    tod_hack_tv = (ulong64)tv.tv_sec * 1000000 + tv.tv_usec;
    tod_hack_tod = ciaatod;
    tod_hack_tod_last = tod_hack_tod;
}
#endif

static void do_tod_hack(int dotod)
{
    struct timeval tv;
    static int oldrate;
    ulong64 t;
    int rate;
    int docount = 0;

    if (tod_hack_enabled == 0)
        return;
    if (tod_hack_enabled > 1)
    {
        tod_hack_enabled--;
        if (tod_hack_enabled == 1)
        {
            Logger::Write(L"TOD HACK enabled\n");
            tod_hack_reset();
        }
        return;
    }

    if (g_curr_conf.cs_ciaatod == 0)
        rate = (int)(vblank_hz + 0.5);
    else if (g_curr_conf.cs_ciaatod == 1)
        rate = 50;
    else if (g_curr_conf.cs_ciaatod == 2)
        rate = 60;
    else
        rate = g_curr_conf.ntscmode ? 60 : 50;

    if (rate <= 0)
        return;
    if (rate != oldrate || ciaatod != tod_hack_tod_last)
    {
        if (ciaatod != 0)
            Logger::Write(L"TOD HACK reset %d,%d %d,%d\n",
                rate, oldrate, ciaatod, tod_hack_tod_last);
        tod_hack_reset();
        oldrate = rate;
        docount = 1;
    }
    if (!dotod && g_curr_conf.cs_ciaatod == 0)
        return;
    gettimeofday(&tv, nullptr);
    t = (ulong64)tv.tv_sec * 1000000 + tv.tv_usec;
    if (t - tod_hack_tv >= 1000000 / rate)
    {
        tod_hack_tv += 1000000 / rate;
        docount = 1;
    }
    if (docount)
    {
        ciaatod++;
        ciaatod &= 0x00ffffff;
        tod_hack_tod_last = ciaatod;
        ciaa_checkalarm(0);
    }
}

static int resetwarning_phase, resetwarning_timer;

static void setcode(byte keycode)
{
    ciaasdr = ~((keycode << 1) | (keycode >> 7));
}

static void sendrw()
{
    setcode(AK_RESETWARNING);
    ciaasdr_unread = 1;
    ciaaicr |= 8;
    RethinkICRA();
    Logger::Write(L"KB: sent reset warning code (phase=%d)\n", resetwarning_phase);
}

int resetwarning_do(int canreset)
{
    if (resetwarning_phase)
    {
        /* just force reset if second reset happens during resetwarning */
        if (canreset)
        {
            resetwarning_phase = 0;
            resetwarning_timer = 0;
        }
        return 0;
    }
    resetwarning_phase = 1;
    resetwarning_timer = maxvpos_nom * 5;
    Logger::Write(L"KB: reset warning triggered\n");
    sendrw();
    return 1;
}

static void resetwarning_check()
{
    if (resetwarning_timer > 0)
    {
        resetwarning_timer--;
        if (resetwarning_timer <= 0)
        {
            Logger::Write(L"KB: reset warning forced reset. Phase=%d\n", resetwarning_phase);
            resetwarning_phase = -1;
            uae_reset(0);
        }
    }
    if (resetwarning_phase == 1)
    {
        if (kback && !(ciaacra & 0x40) && ciaasdr_unread == 2)
        {
            Logger::Write(L"KB: reset warning second phase..\n");
            resetwarning_phase = 2;
            resetwarning_timer = maxvpos_nom * 5;
            sendrw();
        }
    }
    else if (resetwarning_phase == 2)
    {
        if (ciaacra & 0x40)
        {
            resetwarning_phase = 3;
            Logger::Write(L"KB: reset warning SP = output\n");
            resetwarning_timer = 10 * maxvpos_nom * vblank_hz; /* wait max 10s */
        }
    }
    else if (resetwarning_phase == 3)
    {
        if (!(ciaacra & 0x40))
        {
            Logger::Write(L"KB: reset warning end by software. reset.\n");
            resetwarning_phase = -1;
            uae_reset(0);
        }
    }
}

void CIA_hsync_prehandler()
{
}

void CIA_hsync_posthandler(bool dotod)
{
    if (ciabtodon && dotod)
    {
        ciabtod++;
        ciabtod &= 0xFFFFFF;
        ciab_checkalarm(1);
    }

    if (g_curr_conf.tod_hack && ciaatodon)
        do_tod_hack(dotod);

    if (resetwarning_phase)
    {
        resetwarning_check();
        while (keys_available())
            get_next_key();
    }
    else if ((keys_available() || kbstate < 3) && kback && (ciaacra & 0x40) == 0 && (hsync_counter & 15) == 0)
    {
        /*
         * This hack lets one possible ciaaicr cycle go by without any key
         * being read, for every cycle in which a key is pulled out of the
         * queue.  If no hack is used, a lot of key events just get lost
         * when you type fast.  With a simple hack that waits for ciaasdr
         * to be read before feeding it another, it will keep up until the
         * queue gets about 14 characters ahead and then lose events, and
         * the mouse pointer will freeze while typing is being taken in.
         * With this hack, you can type 30 or 40 characters ahead with little
         * or no lossage, and the mouse doesn't get stuck.  The tradeoff is
         * that the total slowness of typing appearing on screen is worse.
         */
        if (ciaasdr_unread == 2)
        {
            ciaasdr_unread = 0;
        }
        else if (ciaasdr_unread == 0)
        {
            switch (kbstate)
            {
                case 0:
                    ciaasdr = 0; /* powerup resync */
                    kbstate++;
                    ciaasdr_unread = 3;
                    break;
                case 1:
                    setcode(AK_INIT_POWERUP);
                    kbstate++;
                    ciaasdr_unread = 3;
                    break;
                case 2:
                    setcode(AK_TERM_POWERUP);
                    kbstate++;
                    ciaasdr_unread = 3;
                    break;
                case 3:
                {
                    ciaasdr = ~get_next_key();
                    ciaasdr_unread = 1;  /* interlock to prevent lost keystrokes */

                    int key = ~ciaasdr;
                    if (Logging::KEYBOARD)
                        Logger::WriteLine(L"CIA key put raw code: %X", key >> 1);

                    break;
                }
            }
            ciaaicr |= 8;
            RethinkICRA();
            sleepyhead = 0;
        }
        else if (!(++sleepyhead & 15))
        {
            if (ciaasdr_unread == 3)
            {
                ciaaicr |= 8;
                RethinkICRA();
            }
            if (ciaasdr_unread < 3)
                ciaasdr_unread = 0;  /* give up on this key event after unread for a long time */
        }
    }
}

static void calc_led(int old_led)
{
    uint c = get_cycles();
    uint t = (c - led_cycle) / CYCLE_UNIT;
    if (old_led)
        led_cycles_on += t;
    else
        led_cycles_off += t;
    led_cycle = c;
}

static void led_vsync()
{
    int v;

    calc_led(led);
    if (led_cycles_on && !led_cycles_off)
        v = 255;
    else if (led_cycles_off && !led_cycles_on)
        v = 0;
    else if (led_cycles_off)
        v = led_cycles_on * 255 / (led_cycles_on + led_cycles_off);
    else
        v = 255;
    if (v < 0)
        v = 0;
    if (v > 255)
        v = 255;
    gui_data.powerled_brightness = v;
    led_cycles_on = 0;
    led_cycles_off = 0;
    if (led_old_brightness != gui_data.powerled_brightness)
    {
        gui_data.powerled = gui_data.powerled_brightness > 127;
        gui_led(LED_POWER, gui_data.powerled);
        led_filter_audio();
    }
    led_old_brightness = gui_data.powerled_brightness;
    led_cycle = get_cycles();
}

void CIA_vsync_prehandler()
{
    led_vsync();
    CIA_handler();
}

void CIA_vsync_posthandler(bool dotod)
{
    #ifdef TOD_HACK
    if (g_curr_conf.tod_hack && tod_hack_enabled == 1)
        return;
    #endif
    if (ciaatodon && dotod)
    {
        ciaatod++;
        ciaatod &= 0xFFFFFF;
        ciaa_checkalarm(1);
    }
    #if 0
    if (vpos == 0)
    {
        Logger::Write(L"%d\n", vsync_counter);
        dumpcia();
    }
    #endif
}

static void bfe001_change()
{
    byte v = ciaapra;
    bool led2;

    v |= ~ciaadra; /* output is high when pin's direction is input */
    led2 = (v & 2) ? 0 : 1;
    if (led2 != led)
    {
        calc_led(led);
        led = led2;
        led_old_brightness = -1;
    }
    if (g_curr_conf.cs_ciaoverlay && (v & 1) != oldovl)
    {
        oldovl = v & 1;
        if (!oldovl)
        {
            map_overlay(1);
        }
        else
        {
            // activate_debugger();
            map_overlay(0);
        }
    }
    if (g_curr_conf.cs_cd32cd && (v & 1) != oldcd32mute)
    {
        oldcd32mute = v & 1;
        akiko_mute(oldcd32mute ? 0 : 1);
    }
}

static byte ReadCIAA(uint addr)
{
    uint tmp;
    int reg = addr & 15;

    compute_passed_time();

    if (Logging::CIAA_R_DEBUG)
        Logger::Write(L"R_CIAA: bfe%x01 %08X\n", reg, M68K_GETPC);

    switch (reg)
    {
        case 0:
            #ifdef ACTION_REPLAY
            action_replay_ciaread();
            #endif
            tmp = DISK_status() & 0x3c;
            tmp |= handle_joystick_buttons(ciaapra, ciaadra);
            tmp |= (ciaapra | (ciaadra ^ 3)) & 0x03;
            tmp = dongle_cia_read(0, reg, tmp);
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFE001 R %02X %s\n", tmp, debuginfo(0));
            #endif

            if (inputrecord_debug & 2)
            {
                if (input_record > 0)
                    inprec_recorddebug_cia(tmp, div10, m68k_getpc());
                else if (input_play > 0)
                    inprec_playdebug_cia(tmp, div10, m68k_getpc());
            }

            return tmp;
        case 1:
            #ifdef PARALLEL_PORT
            if (isprinter() > 0)
            {
                tmp = ciaaprb;
            }
            else if (isprinter() < 0)
            {
                byte v;
                parallel_direct_read_data(&v);
                tmp = v;
                //     #ifdef ARCADIA
                // }
                // else if (arcadia_bios)
                // {
                //     tmp = arcadia_parport(0, ciaaprb, ciaadrb);
                //     #endif
            }
            else if (g_curr_conf.win32_samplersoundcard >= 0)
            {
                tmp = sampler_getsample((ciabpra & 4) ? 1 : 0);
            }
            else
            #endif

            {
                tmp = handle_parport_joystick(0, ciaaprb, ciaadrb);
                tmp = dongle_cia_read(1, reg, tmp);
                #if DONGLE_DEBUG_DEF
                if (notinrom())
                    Logger::Write(L"BFE101 R %02X %s\n", tmp, debuginfo(0));
                #endif
            }
            if (ciaacrb & 2)
            {
                int pb7 = 0;
                if (ciaacrb & 4)
                    pb7 = ciaacrb & 1;
                tmp &= ~0x80;
                tmp |= pb7 ? 0x80 : 00;
            }
            if (ciaacra & 2)
            {
                int pb6 = 0;
                if (ciaacra & 4)
                    pb6 = ciaacra & 1;
                tmp &= ~0x40;
                tmp |= pb6 ? 0x40 : 00;
            }
            return tmp;
        case 2:
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFE201 R %02X %s\n", ciaadra, debuginfo(0));
            #endif
            return ciaadra;
        case 3:
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFE301 R %02X %s\n", ciaadrb, debuginfo(0));
            #endif
            return ciaadrb;
        case 4:
            return (byte)((ciaata - ciaata_passed) & 0xff);
        case 5:
            return (byte)((ciaata - ciaata_passed) >> 8);
        case 6:
            return (byte)((ciaatb - ciaatb_passed) & 0xff);
        case 7:
            return (byte)((ciaatb - ciaatb_passed) >> 8);
        case 8:
            if (ciaatlatch)
            {
                ciaatlatch = 0;
                return (byte)ciaatol;
            }
            else
                return (byte)ciaatod;
        case 9:
            if (ciaatlatch)
                return (byte)(ciaatol >> 8);
            else
                return (byte)(ciaatod >> 8);
        case 10:
            if (!ciaatlatch) /* only if not already latched. A1200 confirmed. (TW) */
            { /* no latching if ALARM is set */
                if (!(ciaacrb & 0x80))
                    ciaatlatch = 1;
                ciaatol = ciaatod;
            }
            return (byte)(ciaatol >> 16);
        case 12:
            if (ciaasdr_unread >= 1)
                ciaasdr_unread = 2;
            return ciaasdr;
        case 13:
            tmp = ciaaicr_reg;
            ciaaicr &= ~ciaaicr_reg;
            ciaaicr_reg = 0;
            RethinkICRA();
            return tmp;
        case 14:
            return ciaacra;
        case 15:
            return ciaacrb;
    }
    return 0;
}

static byte ReadCIAB(uint addr)
{
    uint tmp;
    int reg = addr & 15;

    if (Logging::CIAB_R_DEBUG)
    {
        if ((addr >= 8 && addr <= 10) || Logging::CIAB_R_DEBUG > 1)
            Logger::Write(L"R_CIAB: bfd%x00 %08X\n", reg, M68K_GETPC);
    }

    compute_passed_time();

    switch (reg)
    {
        case 0:
            tmp = 0;
            #ifdef SERIAL_PORT
            if (g_curr_conf.use_serial)
                tmp = serial_readstatus(ciabdra);
            #endif
            #ifdef PARALLEL_PORT
            if (isprinter() > 0)
            {
                // tmp |= ciabpra & (0x04 | 0x02 | 0x01);
                tmp &= ~3; // clear BUSY and PAPEROUT
                tmp |= 4; // set SELECT
            }
            else if (isprinter() < 0)
            {
                byte v;
                parallel_direct_read_status(&v);
                tmp |= v & 7;
            }
            else
            {
                tmp |= handle_parport_joystick(1, ciabpra, ciabdra);
            }
            #endif
            tmp = dongle_cia_read(1, reg, tmp);
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFD000 R %02X %s\n", tmp, debuginfo(0));
            #endif
            return tmp;
        case 1:
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFD100 R %02X %s\n", ciabprb, debuginfo(0));
            #endif
            tmp = ciabprb;
            tmp = dongle_cia_read(1, reg, tmp);
            if (ciabcrb & 2)
            {
                int pb7 = 0;
                if (ciabcrb & 4)
                    pb7 = ciabcrb & 1;
                tmp &= ~0x80;
                tmp |= pb7 ? 0x80 : 00;
            }
            if (ciabcra & 2)
            {
                int pb6 = 0;
                if (ciabcra & 4)
                    pb6 = ciabcra & 1;
                tmp &= ~0x40;
                tmp |= pb6 ? 0x40 : 00;
            }
            return tmp;
        case 2:
            return ciabdra;
        case 3:
            return ciabdrb;
        case 4:
            return (byte)((ciabta - ciabta_passed) & 0xff);
        case 5:
            return (byte)((ciabta - ciabta_passed) >> 8);
        case 6:
            return (byte)((ciabtb - ciabtb_passed) & 0xff);
        case 7:
            return (byte)((ciabtb - ciabtb_passed) >> 8);
        case 8:
            if (ciabtlatch)
            {
                ciabtlatch = 0;
                return (byte)ciabtol;
            }
            else
                return (byte)ciabtod;
        case 9:
            if (ciabtlatch)
                return (byte)(ciabtol >> 8);
            else
                return (byte)(ciabtod >> 8);
        case 10:
            if (!ciabtlatch)
            {
                /* no latching if ALARM is set */
                if (!(ciabcrb & 0x80))
                    ciabtlatch = 1;
                ciabtol = ciabtod;
            }
            return (byte)(ciabtol >> 16);
        case 12:
            return ciabsdr;
        case 13:
            tmp = ciabicr_reg;
            ciabicr &= ~ciabicr_reg;
            ciabicr_reg = 0;
            RethinkICRB();
            return tmp;
        case 14:
            // write_log (L"CIABCRA READ %d %x\n", ciabcra, M68K_GETPC);
            return ciabcra;
        case 15:
            return ciabcrb;
    }
    return 0;
}

static void WriteCIAA(ushort addr, byte val)
{
    int reg = addr & 15;

    if (Logging::CIAA_W_DEBUG)
        Logger::Write(L"W_CIAA: bfe%x01 %02X %08X\n", reg, val, M68K_GETPC);

    #ifdef ACTION_REPLAY
    ar_ciaa[reg] = val;
    #endif
    if (!g_curr_conf.cs_ciaoverlay && oldovl)
    {
        map_overlay(1);
        oldovl = 0;
    }
    switch (reg)
    {
        case 0:
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFE001 W %02X %s\n", val, debuginfo(0));
            #endif
            ciaapra = (ciaapra & ~0xc3) | (val & 0xc3);
            bfe001_change();
            handle_cd32_joystick_cia(ciaapra, ciaadra);
            dongle_cia_write(0, reg, val);
            // #ifdef AMAX
            // if (g_curr_conf.amaxromfile[0])
            //     amax_bfe001_write(val, ciaadra);
            // #endif
            break;
        case 1:
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFE101 W %02X %s\n", val, debuginfo(0));
            #endif
            ciaaprb = val;
            dongle_cia_write(0, reg, val);
            #ifdef PARALLEL_PORT
            if (isprinter() > 0)
            {
                doprinter(val);
                cia_parallelack();
            }
            else if (isprinter() < 0)
            {
                parallel_direct_write_data(val, ciaadrb);
                cia_parallelack();
                // #ifdef ARCADIA
                // }
                // else if (arcadia_bios)
                // {
                //     arcadia_parport(1, ciaaprb, ciaadrb);
                // #endif
            }
            #endif
            break;
        case 2:
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFE201 W %02X %s\n", val, debuginfo(0));
            #endif
            ciaadra = val;
            dongle_cia_write(0, reg, val);
            bfe001_change();
            break;
        case 3:
            ciaadrb = val;
            dongle_cia_write(0, reg, val);
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFE301 W %02X %s\n", val, debuginfo(0));
            #endif
            // #ifdef ARCADIA
            // if (arcadia_bios)
            //     arcadia_parport(1, ciaaprb, ciaadrb);
            // #endif
            break;
        case 4:
            CIA_update();
            ciaala = (ciaala & 0xff00) | val;
            CIA_calctimers();
            break;
        case 5:
            CIA_update();
            ciaala = (ciaala & 0xff) | (val << 8);
            if ((ciaacra & 1) == 0)
                ciaata = ciaala;
            if (ciaacra & 8)
            {
                ciaata = ciaala;
                ciaacra |= 1;
                ciaastarta = CIASTARTCYCLESHI;
            }
            CIA_calctimers();
            break;
        case 6:
            CIA_update();
            ciaalb = (ciaalb & 0xff00) | val;
            CIA_calctimers();
            break;
        case 7:
            CIA_update();
            ciaalb = (ciaalb & 0xff) | (val << 8);
            if ((ciaacrb & 1) == 0)
                ciaatb = ciaalb;
            if (ciaacrb & 8)
            {
                ciaatb = ciaalb;
                ciaacrb |= 1;
                ciaastartb = CIASTARTCYCLESHI;
            }
            CIA_calctimers();
            break;
        case 8:
            if (ciaacrb & 0x80)
            {
                ciaaalarm = (ciaaalarm & ~0xff) | val;
            }
            else
            {
                ciaatod = (ciaatod & ~0xff) | val;
                ciaatodon = 1;
                ciaa_checkalarm(0);
            }
            break;
        case 9:
            if (ciaacrb & 0x80)
            {
                ciaaalarm = (ciaaalarm & ~0xff00) | (val << 8);
            }
            else
            {
                ciaatod = (ciaatod & ~0xff00) | (val << 8);
            }
            break;
        case 10:
            if (ciaacrb & 0x80)
            {
                ciaaalarm = (ciaaalarm & ~0xff0000) | (val << 16);
            }
            else
            {
                ciaatod = (ciaatod & ~0xff0000) | (val << 16);
                ciaatodon = 0;
            }
            break;
        case 12:
            CIA_update();
            ciaasdr = val;
            if (ciaacra & 0x40)
            {
                kback = 1;
            }
            else
            {
                ciaasdr_cnt = 0;
            }
            if ((ciaacra & 0x41) == 0x41 && ciaasdr_cnt == 0)
                ciaasdr_cnt = 8 * 2;
            CIA_calctimers();
            break;
        case 13:
            setclr(&ciaaimask, val);
            break;
        case 14:
            CIA_update();
            val &= 0x7f; /* bit 7 is unused */
            if ((val & 1) && !(ciaacra & 1))
                ciaastarta = CIASTARTCYCLESCRA;
            if (!(ciaacra & 0x40) && (val & 0x40))
                kback = 1;
            ciaacra = val;
            if (ciaacra & 0x10)
            {
                ciaacra &= ~0x10;
                ciaata = ciaala;
            }
            CIA_calctimers();
            break;
        case 15:
            CIA_update();
            if ((val & 1) && !(ciaacrb & 1))
                ciaastartb = CIASTARTCYCLESCRA;
            ciaacrb = val;
            if (ciaacrb & 0x10)
            {
                ciaacrb &= ~0x10;
                ciaatb = ciaalb;
            }
            CIA_calctimers();
            break;
    }
}

static void WriteCIAB(ushort addr, byte val)
{
    int reg = addr & 15;

    if (Logging::CIAB_W_DEBUG)
    {
        if ((addr >= 8 && addr <= 10) || Logging::CIAB_W_DEBUG > 1)
            Logger::Write(L"W_CIAB: bfd%x00 %02X %08X\n", reg, val, M68K_GETPC);
    }
    #ifdef ACTION_REPLAY
    ar_ciab[reg] = val;
    #endif
    switch (reg)
    {
        case 0:
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFD000 W %02X %s\n", val, debuginfo(0));
            #endif
            dongle_cia_write(1, reg, val);
            ciabpra = val;
            #ifdef SERIAL_PORT
            if (g_curr_conf.use_serial)
                serial_writestatus(ciabpra, ciabdra);
            #endif
            #ifdef PARALLEL_PORT
            if (isprinter() < 0)
                parallel_direct_write_status(val, ciabdra);
            #endif
            break;
        case 1:
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFD100 W %02X %s\n", val, debuginfo(0));
            #endif
            dongle_cia_write(1, reg, val);
            ciabprb = val;
            DISK_select(val);
            break;
        case 2:
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFD200 W %02X %s\n", val, debuginfo(0));
            #endif
            dongle_cia_write(1, reg, val);
            ciabdra = val;
            #ifdef SERIAL_PORT
            if (g_curr_conf.use_serial)
                serial_writestatus(ciabpra, ciabdra);
            #endif
            break;
        case 3:
            #if DONGLE_DEBUG_DEF
            if (notinrom())
                Logger::Write(L"BFD300 W %02X %s\n", val, debuginfo(0));
            #endif
            dongle_cia_write(1, reg, val);
            ciabdrb = val;
            break;
        case 4:
            CIA_update();
            ciabla = (ciabla & 0xff00) | val;
            CIA_calctimers();
            break;
        case 5:
            CIA_update();
            ciabla = (ciabla & 0xff) | (val << 8);
            if ((ciabcra & 1) == 0)
                ciabta = ciabla;
            if (ciabcra & 8)
            {
                ciabta = ciabla;
                ciabcra |= 1;
                ciabstarta = CIASTARTCYCLESHI;
            }
            CIA_calctimers();
            break;
        case 6:
            CIA_update();
            ciablb = (ciablb & 0xff00) | val;
            CIA_calctimers();
            break;
        case 7:
            CIA_update();
            ciablb = (ciablb & 0xff) | (val << 8);
            if ((ciabcrb & 1) == 0)
                ciabtb = ciablb;
            if (ciabcrb & 8)
            {
                ciabtb = ciablb;
                ciabcrb |= 1;
                ciabstartb = CIASTARTCYCLESHI;
            }
            CIA_calctimers();
            break;
        case 8:
            if (ciabcrb & 0x80)
            {
                ciabalarm = (ciabalarm & ~0xff) | val;
            }
            else
            {
                ciabtod = (ciabtod & ~0xff) | val;
                ciabtodon = 1;
                ciab_checkalarm(0);
            }
            break;
        case 9:
            if (ciabcrb & 0x80)
            {
                ciabalarm = (ciabalarm & ~0xff00) | (val << 8);
            }
            else
            {
                ciabtod = (ciabtod & ~0xff00) | (val << 8);
            }
            break;
        case 10:
            if (ciabcrb & 0x80)
            {
                ciabalarm = (ciabalarm & ~0xff0000) | (val << 16);
            }
            else
            {
                ciabtod = (ciabtod & ~0xff0000) | (val << 16);
                ciabtodon = 0;
            }
            break;
        case 12:
            CIA_update();
            ciabsdr = val;
            if ((ciabcra & 0x40) == 0)
                ciabsdr_cnt = 0;
            if ((ciabcra & 0x41) == 0x41 && ciabsdr_cnt == 0)
                ciabsdr_cnt = 8 * 2;
            CIA_calctimers();
            break;
        case 13:
            setclr(&ciabimask, val);
            break;
        case 14:
            CIA_update();
            val &= 0x7f; /* bit 7 is unused */
            if ((val & 1) && !(ciabcra & 1))
                ciabstarta = CIASTARTCYCLESCRA;
            ciabcra = val;
            if (ciabcra & 0x10)
            {
                ciabcra &= ~0x10;
                ciabta = ciabla;
            }
            CIA_calctimers();
            break;
        case 15:
            CIA_update();
            if ((val & 1) && !(ciabcrb & 1))
                ciabstartb = CIASTARTCYCLESCRA;
            ciabcrb = val;
            if (ciabcrb & 0x10)
            {
                ciabcrb &= ~0x10;
                ciabtb = ciablb;
            }
            CIA_calctimers();
            break;
    }
}

void CIA_reset()
{
    #ifdef TOD_HACK
    tod_hack_tv = 0;
    tod_hack_tod = 0;
    tod_hack_enabled = 0;
    if (g_curr_conf.tod_hack)
        tod_hack_enabled = 312 * 50 * 10;
    #endif

    kback = 1;
    serbits = 0;
    oldovl = 1;
    oldcd32mute = 1;
    oldled = true;
    resetwarning_phase = resetwarning_timer = 0;

    if (!savestate_state)
    {
        kbstate = 0;
        ciaasdr_unread = 0;
        sleepyhead = 0;
        ciaatlatch = ciabtlatch = 0;
        ciaapra = 0;
        ciaadra = 0;
        ciaatod = ciabtod = 0;
        ciaatodon = ciabtodon = 0;
        ciaaicr = ciabicr = ciaaimask = ciabimask = 0;
        ciaacra = ciaacrb = ciabcra = ciabcrb = 0x4; /* outmode = toggle; */
        ciaala = ciaalb = ciabla = ciablb = ciaata = ciaatb = ciabta = ciabtb = 0xFFFF;
        ciaaalarm = ciabalarm = 0;
        ciabpra = 0x8C;
        ciabdra = 0;
        div10 = 0;
        ciaasdr_cnt = 0;
        ciaasdr = 0;
        ciabsdr_cnt = 0;
        ciabsdr = 0;
        ciaata_passed = ciaatb_passed = ciabta_passed = ciabtb_passed = 0;
        CIA_calctimers();
        DISK_select_set(ciabprb);
    }
    map_overlay(0);
    #ifdef SERIAL_PORT
    if (g_curr_conf.use_serial && !savestate_state)
        serial_dtr_off();  /* Drop DTR at reset */
    #endif
    if (savestate_state)
    {
        bfe001_change();
        if (!g_curr_conf.cs_ciaoverlay)
        {
            map_overlay(1);
            oldovl = false;
        }
    }
    #ifdef CD32
    if (!isrestore())
    {
        akiko_reset();
        if (!akiko_init())
        {
            g_curr_conf.cs_cd32cd = 0;
            g_changed_conf.cs_cd32cd = 0;
        }
    }
    #endif
}

void dumpcia()
{
    Logger::ConsoleOut(L"A: CRA %02x CRB %02x ICR %02x IM %02x TA %04x (%04x) TB %04x (%04x)\n",
        ciaacra, ciaacrb, ciaaicr, ciaaimask, ciaata, ciaala, ciaatb, ciaalb);
    Logger::ConsoleOut(L"TOD %06x (%06x) ALARM %06x %c%c CYC=%08X\n",
        ciaatod, ciaatol, ciaaalarm, ciaatlatch ? 'L' : ' ', ciaatodon ? ' ' : 'S', get_cycles());
    Logger::ConsoleOut(L"B: CRA %02x CRB %02x ICR %02x IM %02x TA %04x (%04x) TB %04x (%04x)\n",
        ciabcra, ciabcrb, ciaaicr, ciabimask, ciabta, ciabla, ciabtb, ciablb);
    Logger::ConsoleOut(L"TOD %06x (%06x) ALARM %06x %c%c CLK=%d\n",
        ciabtod, ciabtol, ciabalarm, ciabtlatch ? 'L' : ' ', ciabtodon ? ' ' : 'S', div10 / CYCLE_UNIT);
}

/* CIA memory access */

static uint REGPARAM3 cia_lget(uae_ptr) REGPARAM;
static uint REGPARAM3 cia_wget(uae_ptr) REGPARAM;
static uint REGPARAM3 cia_bget(uae_ptr) REGPARAM;
static uint REGPARAM3 cia_lgeti(uae_ptr) REGPARAM;
static uint REGPARAM3 cia_wgeti(uae_ptr) REGPARAM;
static void REGPARAM3 cia_lput(uae_ptr, uint) REGPARAM;
static void REGPARAM3 cia_wput(uae_ptr, uint) REGPARAM;
static void REGPARAM3 cia_bput(uae_ptr, uint) REGPARAM;

addrbank cia_bank = {
    cia_lget, cia_wget, cia_bget,
    cia_lput, cia_wput, cia_bput,
    default_xlate, default_check, nullptr, L"CIA",
    cia_lgeti, cia_wgeti, ABFLAG_IO
};

//  Gayle does not enable CIA /CS lines if both CIAs are selected
//  Non-Gayle based Amigas enable both CIAs in this situation

static __forceinline int isgayle()
{
    return (g_curr_conf.cs_ide == IDE_A600A1200 || g_curr_conf.cs_pcmcia);
}

static void cia_wait_pre()
{
    if (g_curr_conf.jit_cache_size)
        return;

    #ifndef CUSTOM_SIMPLE
    int div = (get_cycles() - eventtab[ev_cia].oldcycles) % DIV10;
    int cycles;

    if (div >= DIV10 * ECLOCK_DATA_CYCLE / 10)
    {
        cycles = DIV10 - div;
        cycles += DIV10 * ECLOCK_DATA_CYCLE / 10;
    }
    else if (div)
    {
        cycles = DIV10 + DIV10 * ECLOCK_DATA_CYCLE / 10 - div;
    }
    else
    {
        cycles = DIV10 * ECLOCK_DATA_CYCLE / 10 - div;
    }

    if (cycles)
    {
        if (g_curr_conf.cpu_cycle_exact)
            x_do_cycles_pre(cycles);
        else
            do_cycles(cycles);
    }
    #endif
}

static void cia_wait_post(uint value)
{
    if (g_curr_conf.jit_cache_size)
    {
        do_cycles(8 * CYCLE_UNIT / 2);
    }
    else
    {
        int c = 6 * CYCLE_UNIT / 2;
        if (g_curr_conf.cpu_cycle_exact)
            x_do_cycles_post(c, value);
        else
            do_cycles(c);
    }
}

static uint REGPARAM2 cia_bget(uae_ptr addr)
{
    int r = (addr & 0xf00) >> 8;
    byte v;

    #ifdef JIT
    special_mem |= S_READ;
    #endif
    cia_wait_pre();
    v = 0xff;
    switch ((addr >> 12) & 3)
    {
        case 0:
            if (!isgayle())
                v = (addr & 1) ? ReadCIAA(r) : ReadCIAB(r);
            break;
        case 1:
            v = (addr & 1) ? 0xff : ReadCIAB(r);
            break;
        case 2:
            v = (addr & 1) ? ReadCIAA(r) : 0xff;
            break;
        case 3:
            if (g_curr_conf.cpu_model == 68000 && g_curr_conf.cpu_compatible)
                v = (addr & 1) ? regs.irc : regs.irc >> 8;
            if (warned > 0)
            {
                Logger::Write(L"cia_bget: unknown CIA address %x PC=%x\n", addr, M68K_GETPC);
                warned--;
            }
            break;
    }
    cia_wait_post(v);
    return v;
}

static uint REGPARAM2 cia_wget(uae_ptr addr)
{
    int r = (addr & 0xf00) >> 8;
    ushort v;

    #ifdef JIT
    special_mem |= S_READ;
    #endif
    cia_wait_pre();
    v = 0xffff;
    switch ((addr >> 12) & 3)
    {
        case 0:
            if (!isgayle())
                v = (ReadCIAB(r) << 8) | ReadCIAA(r);
            break;
        case 1:
            v = (ReadCIAB(r) << 8) | 0xff;
            break;
        case 2:
            v = (0xff << 8) | ReadCIAA(r);
            break;
        case 3:
            if (g_curr_conf.cpu_model == 68000 && g_curr_conf.cpu_compatible)
                v = regs.irc;
            if (warned > 0)
            {
                Logger::Write(L"cia_wget: unknown CIA address %x PC=%x\n", addr, M68K_GETPC);
                warned--;
            }
            break;
    }
    cia_wait_post(v);
    return v;
}

static uint REGPARAM2 cia_lget(uae_ptr addr)
{
    uint v;
    v = cia_wget(addr) << 16;
    v |= cia_wget(addr + 2);
    return v;
}

static uint REGPARAM2 cia_wgeti(uae_ptr addr)
{
    if (g_curr_conf.cpu_model >= 68020)
        return dummy_wgeti(addr);
    return cia_wget(addr);
}
static uint REGPARAM2 cia_lgeti(uae_ptr addr)
{
    if (g_curr_conf.cpu_model >= 68020)
        return dummy_lgeti(addr);
    return cia_lget(addr);
}

static void REGPARAM2 cia_bput(uae_ptr addr, uint value)
{
    int r = (addr & 0xf00) >> 8;

    #ifdef JIT
    special_mem |= S_WRITE;
    #endif
    cia_wait_pre();
    if (!isgayle() || (addr & 0x3000) != 0)
    {
        if ((addr & 0x2000) == 0)
            WriteCIAB(r, value);
        if ((addr & 0x1000) == 0)
            WriteCIAA(r, value);
        if (((addr & 0x3000) == 0x3000) && warned > 0)
        {
            Logger::Write(L"cia_bput: unknown CIA address %x %x\n", addr, value);
            warned--;
        }
    }
    cia_wait_post(value);
}

static void REGPARAM2 cia_wput(uae_ptr addr, uint value)
{
    int r = (addr & 0xf00) >> 8;

    #ifdef JIT
    special_mem |= S_WRITE;
    #endif
    cia_wait_pre();
    if (!isgayle() || (addr & 0x3000) != 0)
    {
        if ((addr & 0x2000) == 0)
            WriteCIAB(r, value >> 8);
        if ((addr & 0x1000) == 0)
            WriteCIAA(r, value & 0xff);
        if (((addr & 0x3000) == 0x3000) && warned > 0)
        {
            Logger::Write(L"cia_wput: unknown CIA address %x %x\n", addr, value);
            warned--;
        }
    }
    cia_wait_post(value);
}

static void REGPARAM2 cia_lput(uae_ptr addr, uint value)
{
    cia_wput(addr, value >> 16);
    cia_wput(addr + 2, value & 0xffff);
}

/* battclock memory access */

static uint REGPARAM3 clock_lget(uae_ptr) REGPARAM;
static uint REGPARAM3 clock_wget(uae_ptr) REGPARAM;
static uint REGPARAM3 clock_bget(uae_ptr) REGPARAM;
static void REGPARAM3 clock_lput(uae_ptr, uint) REGPARAM;
static void REGPARAM3 clock_wput(uae_ptr, uint) REGPARAM;
static void REGPARAM3 clock_bput(uae_ptr, uint) REGPARAM;

addrbank clock_bank = {
    clock_lget, clock_wget, clock_bget,
    clock_lput, clock_wput, clock_bput,
    default_xlate, default_check, nullptr, L"Battery backed up clock (none)",
    dummy_lgeti, dummy_wgeti, ABFLAG_IO
};

static uint clock_control_d;
static uint clock_control_e;
static uint clock_control_f;

#define RF5C01A_RAM_SIZE 16
static byte rtc_memory[RF5C01A_RAM_SIZE], rtc_alarm[RF5C01A_RAM_SIZE];

static void write_battclock()
{
    struct zfile* f = zfile_fopen(g_curr_conf.flashfile, L"rb+", ZFD_NORMAL);
    if (!f)
    {
        f = zfile_fopen(g_curr_conf.flashfile, L"wb", 0);
        if (f)
        {
            zfile_fwrite(rtc_memory, RF5C01A_RAM_SIZE, 1, f);
            zfile_fwrite(rtc_alarm, RF5C01A_RAM_SIZE, 1, f);
            zfile_fclose(f);
        }
        return;
    }
    zfile_fseek(f, 0, SEEK_END);
    if (zfile_ftell(f) <= 2 * RF5C01A_RAM_SIZE)
    {
        zfile_fseek(f, 0, SEEK_SET);
        zfile_fwrite(rtc_memory, RF5C01A_RAM_SIZE, 1, f);
        zfile_fwrite(rtc_alarm, RF5C01A_RAM_SIZE, 1, f);
    }
    zfile_fclose(f);
}

void rtc_hardreset()
{
    if (g_curr_conf.cs_rtc == 1)   /* MSM6242B */
    {
        clock_bank.name = L"Battery backed up clock (MSM6242B)";
        clock_control_d = 0x1;
        clock_control_e = 0;
        clock_control_f = 0x4; /* 24/12 */
    }
    else if (g_curr_conf.cs_rtc == 2)     /* RF5C01A */
    {
        clock_bank.name = L"Battery backed up clock (RF5C01A)";
        clock_control_d = 0x4; /* Timer EN */
        clock_control_e = 0;
        clock_control_f = 0;
        memset(rtc_memory, 0, RF5C01A_RAM_SIZE);
        memset(rtc_alarm, 0, RF5C01A_RAM_SIZE);
        #if 0
        struct zfile* f;
        f = zfile_fopen(g_curr_conf.flashfile, "rb", ZFD_NORMAL);
        if (f)
        {
            zfile_fread(rtc_memory, RF5C01A_RAM_SIZE, 1, f);
            zfile_fread(rtc_alarm, RF5C01A_RAM_SIZE, 1, f);
            zfile_fclose(f);
        }
        #endif
    }
}

static uint REGPARAM2 clock_lget(uae_ptr addr)
{
    return (clock_wget(addr) << 16) | clock_wget(addr + 2);
}

static uint REGPARAM2 clock_wget(uae_ptr addr)
{
    return (clock_bget(addr) << 8) | clock_bget(addr + 1);
}

static uint REGPARAM2 clock_bget(uae_ptr addr)
{
    time_t t;
    struct tm* ct;

    #ifdef JIT
    special_mem |= S_READ;
    #endif
    //  Logger::Write(L"R: %x (%x), PC=%08x\n", addr, (addr & 0xff) >> 2, M68K_GETPC);
    #ifdef CDTV
    if (g_curr_conf.cs_cdtvram && addr >= 0xdc8000)
        return cdtv_battram_read(addr);
    #endif
    addr &= 0x3f;
    if ((addr & 3) == 2 || (addr & 3) == 0 || g_curr_conf.cs_rtc == 0)
    {
        int v = 0;
        if (g_curr_conf.cpu_model == 68000 && g_curr_conf.cpu_compatible)
            v = regs.irc >> 8;
        return v;
    }
    t = time(0);
    // t += g_curr_conf.cs_rtc_adjust;
    ct = localtime(&t);
    addr >>= 2;
    if (g_curr_conf.cs_rtc == 1)   /* MSM6242B */
    {
        switch (addr)
        {
            case 0x0: return ct->tm_sec % 10;
            case 0x1: return ct->tm_sec / 10;
            case 0x2: return ct->tm_min % 10;
            case 0x3: return ct->tm_min / 10;
            case 0x4: return ct->tm_hour % 10;
            case 0x5: return ct->tm_hour / 10;
            case 0x6: return ct->tm_mday % 10;
            case 0x7: return ct->tm_mday / 10;
            case 0x8: return (ct->tm_mon + 1) % 10;
            case 0x9: return (ct->tm_mon + 1) / 10;
            case 0xA: return ct->tm_year % 10;
            case 0xB: return ct->tm_year / 10;
            case 0xC: return ct->tm_wday;
            case 0xD: return clock_control_d;
            case 0xE: return clock_control_e;
            case 0xF: return clock_control_f;
        }
    }
    else if (g_curr_conf.cs_rtc == 2)     /* RF5C01A */
    {
        int bank = clock_control_d & 3;
        /* memory access */
        if (bank >= 2 && addr < 0x0d)
            return (rtc_memory[addr] >> ((bank == 2) ? 0 : 4)) & 0x0f;
        /* alarm */
        if (bank == 1 && addr < 0x0d)
            return rtc_alarm[addr];
        switch (addr)
        {
            case 0x0: return ct->tm_sec % 10;
            case 0x1: return ct->tm_sec / 10;
            case 0x2: return ct->tm_min % 10;
            case 0x3: return ct->tm_min / 10;
            case 0x4: return ct->tm_hour % 10;
            case 0x5: return ct->tm_hour / 10;
            case 0x6: return ct->tm_wday;
            case 0x7: return ct->tm_mday % 10;
            case 0x8: return ct->tm_mday / 10;
            case 0x9: return (ct->tm_mon + 1) % 10;
            case 0xA: return (ct->tm_mon + 1) / 10;
            case 0xB: return ct->tm_year % 10;
            case 0xC: return ct->tm_year / 10;
            case 0xD: return clock_control_d;
                /* E and F = write-only */
        }
    }
    return 0;
}

static void REGPARAM2 clock_lput(uae_ptr addr, uint value)
{
    clock_wput(addr, value >> 16);
    clock_wput(addr + 2, value);
}

static void REGPARAM2 clock_wput(uae_ptr addr, uint value)
{
    clock_bput(addr, value >> 8);
    clock_bput(addr + 1, value);
}

static void REGPARAM2 clock_bput(uae_ptr addr, uint value)
{
    #ifdef JIT
    special_mem |= S_WRITE;
    #endif
    //  Logger::Write(L"W: %x (%x): %x, PC=%08x\n", addr, (addr & 0xff) >> 2, value & 0xff, M68K_GETPC);
    #ifdef CDTV
    if (g_curr_conf.cs_cdtvram && addr >= 0xdc8000)
    {
        cdtv_battram_write(addr, value);
        return;
    }
    #endif
    addr &= 0x3f;
    if ((addr & 1) != 1 || g_curr_conf.cs_rtc == 0)
        return;
    addr >>= 2;
    value &= 0x0f;
    if (g_curr_conf.cs_rtc == 1)   /* MSM6242B */
    {
        switch (addr)
        {
            case 0xD: clock_control_d = value & (1 | 8);
                break;
            case 0xE: clock_control_e = value;
                break;
            case 0xF: clock_control_f = value;
                break;
        }
    }
    else if (g_curr_conf.cs_rtc == 2)     /* RF5C01A */
    {
        int bank = clock_control_d & 3;
        /* memory access */
        if (bank >= 2 && addr < 0x0d)
        {
            rtc_memory[addr] &= ((bank == 2) ? 0xf0 : 0x0f);
            rtc_memory[addr] |= value << ((bank == 2) ? 0 : 4);
            #if 0
            byte ov = rtc_memory[addr];
            if (rtc_memory[addr] != ov)
                write_battclock();
            #endif
            return;
        }
        /* alarm */
        if (bank == 1 && addr < 0x0d)
        {
            rtc_alarm[addr] = value;
            rtc_alarm[0] = rtc_alarm[1] = rtc_alarm[9] = rtc_alarm[12] = 0;
            rtc_alarm[3] &= ~0x8;
            rtc_alarm[5] &= ~0xc;
            rtc_alarm[6] &= ~0x8;
            rtc_alarm[8] &= ~0xc;
            rtc_alarm[10] &= ~0xe;
            rtc_alarm[11] &= ~0xc;
            #if 0
            byte ov = rtc_alarm[addr];
            if (rtc_alarm[addr] != value)
                write_battclock();
            #endif
            return;
        }
        switch (addr)
        {
            case 0xD: clock_control_d = value;
                break;
            case 0xE: clock_control_e = value;
                break;
            case 0xF: clock_control_f = value;
                break;
        }
    }
}

#ifdef SAVESTATE

/* CIA-A and CIA-B save/restore code */

static void save_cia_prepare()
{
    CIA_update_check();
    CIA_calctimers();
    compute_passed_time();
}

void restore_cia_finish()
{
    eventtab[ev_cia].oldcycles = get_cycles();
    CIA_update();
    CIA_calctimers();
    compute_passed_time();
    // dumpcia();
    DISK_select_set(ciabprb);
}

byte* restore_cia(int num, byte* src)
{
    byte b;
    ushort w;
    uint l;

    /* CIA registers */
    b = restore_u8();                   /* 0 PRA */
    if (num)
        ciabpra = b;
    else
        ciaapra = b;
    b = restore_u8();                   /* 1 PRB */
    if (num)
        ciabprb = b;
    else
        ciaaprb = b;
    b = restore_u8();                   /* 2 DDRA */
    if (num)
        ciabdra = b;
    else
        ciaadra = b;
    b = restore_u8();                   /* 3 DDRB */
    if (num)
        ciabdrb = b;
    else
        ciaadrb = b;
    w = restore_u16();                  /* 4 TA */
    if (num)
        ciabta = w;
    else
        ciaata = w;
    w = restore_u16();                  /* 6 TB */
    if (num)
        ciabtb = w;
    else
        ciaatb = w;
    l = restore_u8();                   /* 8/9/A TOD */
    l |= restore_u8() << 8;
    l |= restore_u8() << 16;
    if (num)
        ciabtod = l;
    else
        ciaatod = l;
    restore_u8();                   /* B unused */
    b = restore_u8();                   /* C SDR */
    if (num)
        ciabsdr = b;
    else
        ciaasdr = b;
    b = restore_u8();                   /* D ICR INFORMATION (not mask!) */
    if (num)
        ciabicr = b;
    else
        ciaaicr = b;
    b = restore_u8();                   /* E CRA */
    if (num)
        ciabcra = b;
    else
        ciaacra = b;
    b = restore_u8();                   /* F CRB */
    if (num)
        ciabcrb = b;
    else
        ciaacrb = b;

    /* CIA internal data */

    b = restore_u8();                   /* ICR MASK */
    if (num)
        ciabimask = b;
    else
        ciaaimask = b;
    w = restore_u8();                   /* timer A latch */
    w |= restore_u8() << 8;
    if (num)
        ciabla = w;
    else
        ciaala = w;
    w = restore_u8();                   /* timer B latch */
    w |= restore_u8() << 8;
    if (num)
        ciablb = w;
    else
        ciaalb = w;
    w = restore_u8();                   /* TOD latched value */
    w |= restore_u8() << 8;
    w |= restore_u8() << 16;
    if (num)
        ciabtol = w;
    else
        ciaatol = w;
    l = restore_u8();                   /* alarm */
    l |= restore_u8() << 8;
    l |= restore_u8() << 16;
    if (num)
        ciabalarm = l;
    else
        ciaaalarm = l;
    b = restore_u8();
    if (num)
        ciabtlatch = b & 1;
    else
        ciaatlatch = b & 1;                                 /* is TOD latched? */
    if (num)
        ciabtodon = b & 2;
    else
        ciaatodon = b & 2;                                  /* is TOD stopped? */
    b = restore_u8();
    if (num)
        div10 = CYCLE_UNIT * b;
    b = restore_u8();
    if (num)
        ciabsdr_cnt = b;
    else
        ciaasdr_cnt = b;
    return src;
}

byte* save_cia(int num, int* len, byte* dstptr)
{
    byte* dstbak, * dst, b;
    ushort t;

    if (dstptr)
        dstbak = dst = dstptr;
    else
        dstbak = dst = xmalloc(byte, 1000);

    save_cia_prepare();

    /* CIA registers */

    b = num ? ciabpra : ciaapra;                /* 0 PRA */
    save_u8(b);
    b = num ? ciabprb : ciaaprb;                /* 1 PRB */
    save_u8(b);
    b = num ? ciabdra : ciaadra;                /* 2 DDRA */
    save_u8(b);
    b = num ? ciabdrb : ciaadrb;                /* 3 DDRB */
    save_u8(b);
    t = (num ? ciabta - ciabta_passed : ciaata - ciaata_passed); /* 4 TA */
    save_u16(t);
    t = (num ? ciabtb - ciabtb_passed : ciaatb - ciaatb_passed); /* 6 TB */
    save_u16(t);
    b = (num ? ciabtod : ciaatod);          /* 8 TODL */
    save_u8(b);
    b = (num ? ciabtod >> 8 : ciaatod >> 8);        /* 9 TODM */
    save_u8(b);
    b = (num ? ciabtod >> 16 : ciaatod >> 16);      /* A TODH */
    save_u8(b);
    save_u8(0);                         /* B unused */
    b = num ? ciabsdr : ciaasdr;                /* C SDR */
    save_u8(b);
    b = num ? ciabicr : ciaaicr;                /* D ICR INFORMATION (not mask!) */
    save_u8(b);
    b = num ? ciabcra : ciaacra;                /* E CRA */
    save_u8(b);
    b = num ? ciabcrb : ciaacrb;                /* F CRB */
    save_u8(b);

    /* CIA internal data */

    save_u8(num ? ciabimask : ciaaimask);           /* ICR */
    b = (num ? ciabla : ciaala);            /* timer A latch LO */
    save_u8(b);
    b = (num ? ciabla >> 8 : ciaala >> 8);      /* timer A latch HI */
    save_u8(b);
    b = (num ? ciablb : ciaalb);            /* timer B latch LO */
    save_u8(b);
    b = (num ? ciablb >> 8 : ciaalb >> 8);      /* timer B latch HI */
    save_u8(b);
    b = (num ? ciabtol : ciaatol);          /* latched TOD LO */
    save_u8(b);
    b = (num ? ciabtol >> 8 : ciaatol >> 8);        /* latched TOD MED */
    save_u8(b);
    b = (num ? ciabtol >> 16 : ciaatol >> 16);      /* latched TOD HI */
    save_u8(b);
    b = (num ? ciabalarm : ciaaalarm);          /* alarm LO */
    save_u8(b);
    b = (num ? ciabalarm >> 8 : ciaaalarm >> 8);    /* alarm MED */
    save_u8(b);
    b = (num ? ciabalarm >> 16 : ciaaalarm >> 16);  /* alarm HI */
    save_u8(b);
    b = 0;
    if (num)
        b |= ciabtlatch ? 1 : 0;
    else
        b |= ciaatlatch ? 1 : 0;  /* is TOD latched? */
    if (num)
        b |= ciabtodon ? 2 : 0;
    else
        b |= ciaatodon ? 2 : 0;   /* TOD stopped? */
    save_u8(b);
    save_u8(num ? div10 / CYCLE_UNIT : 0);
    save_u8(num ? ciabsdr_cnt : ciaasdr_cnt);
    *len = dst - dstbak;
    return dstbak;
}

byte* save_keyboard(int* len, byte* dstptr)
{
    byte* dst, * dstbak;
    if (dstptr)
        dstbak = dst = dstptr;
    else
        dstbak = dst = xmalloc(byte, 4 + 4 + 1 + 1 + 1);
    save_u32(getcapslockstate() ? 1 : 0);
    save_u32(1);
    save_u8(kbstate);
    save_u8(ciaasdr_unread);
    save_u8(sleepyhead);
    *len = dst - dstbak;
    return dstbak;
}

byte* restore_keyboard(byte* src)
{
    setcapslockstate(restore_u32() & 1);
    uint v = restore_u32();
    kbstate = restore_u8();
    ciaasdr_unread = restore_u8();
    sleepyhead = restore_u8();
    if (!(v & 1))
        kbstate = 3;
    return src;
}

#endif /* SAVESTATE */